# coding=utf8
# XBR Network - Copyright (c) typedef int GmbH. Licensed under EUPLv1.2.

import argparse
import binascii
import sys
import uuid
from pprint import pformat

import eth_keys
import txaio
import web3

txaio.use_twisted()

from autobahn.twisted.wamp import ApplicationRunner, ApplicationSession
from autobahn.wamp import cryptosign
from autobahn.wamp.serializer import CBORSerializer
from twisted.internet import reactor
from twisted.internet.error import ReactorNotRunning


class XbrDelegate(ApplicationSession):
    def __init__(self, config=None):
        self.log.info("{klass}.__init__(config={config})", klass=self.__class__.__name__, config=config)

        ApplicationSession.__init__(self, config)

        self._ethkey_raw = config.extra["ethkey"]
        self._ethkey = eth_keys.keys.PrivateKey(self._ethkey_raw)
        self._ethadr = web3.Web3.toChecksumAddress(self._ethkey.public_key.to_canonical_address())

        self.log.info("Client (delegate) Ethereum key loaded (adr=0x{adr})", adr=self._ethadr)

        self._key = cryptosign.CryptosignKey.from_bytes(config.extra["cskey"])
        self.log.info(
            "Client (delegate) WAMP-cryptosign authentication key loaded (pubkey=0x{pubkey})",
            pubkey=self._key.public_key(),
        )

        self._running = True

    def onUserError(self, fail, msg):
        self.log.error(msg)
        self.leave("wamp.error", msg)

    def onConnect(self):
        self.log.info("{klass}.onConnect()", klass=self.__class__.__name__)

        authextra = {
            "pubkey": self._key.public_key(),
            "trustroot": None,
            "challenge": None,
            "channel_binding": "tls-unique",
        }
        self.join(self.config.realm, authmethods=["cryptosign"], authextra=authextra)

    def onChallenge(self, challenge):
        self.log.info("{klass}.onChallenge(challenge={challenge})", klass=self.__class__.__name__, challenge=challenge)

        if challenge.method == "cryptosign":
            # sign the challenge with our private key.
            channel_id_type = self.config.extra.get("channel_binding", None)
            channel_id = self.transport.transport_details.channel_id.get(channel_id_type, None)
            signed_challenge = self._key.sign_challenge(
                challenge, channel_id=channel_id, channel_id_type=channel_id_type
            )
            return signed_challenge
        else:
            raise RuntimeError("unable to process authentication method {}".format(challenge.method))

    async def onJoin(self, details):
        self.log.info("{klass}.onJoin(details={details})", klass=self.__class__.__name__, details=details)

        try:
            assert details.authrole == "member"

            # WAMP authid on xbrnetwork follows this format: "member-"
            member_id = details.authid[7:]
            member_id = uuid.UUID(member_id)

            config = await self.call("xbr.network.get_config")
            self.log.info("SUCCESS: backend config\n\n{config}\n", config=pformat(config))

            status = await self.call("xbr.network.get_status")
            self.log.info("SUCCESS: backend status\n\n{status}\n", status=pformat(status))

            member_data = await self.call("xbr.network.get_member", member_id.bytes)
            self.log.info("SUCCESS: got member information\n\n{member_data}\n", member_data=pformat(member_data))

            member_adr = member_data["address"]
            member_data2 = await self.call("xbr.network.get_member_by_wallet", member_adr)
            self.log.info("SUCCESS: got member information\n\n{member_data2}\n", member_data2=pformat(member_data2))

            assert member_data == member_data2

            pubkeys = await self.call("xbr.network.get_member_logins", member_id.bytes)
            self.log.info("SUCCESS: got list of logins for member:\n\n{pubkeys}\n", pubkeys=pformat(pubkeys))

            for pubkey in pubkeys:
                userkey = await self.call("xbr.network.get_member_login", member_id.bytes, pubkey)
                self.log.info("SUCCESS: got userkey:\n\n{userkey}\n", userkey=pformat(userkey))

        except Exception as e:
            self.log.failure()
            self.config.extra["error"] = e
        finally:
            self.leave()

    def onLeave(self, details):
        self.log.info("{klass}.onLeave(details={details})", klass=self.__class__.__name__, details=details)

        self._running = False

        if details.reason == "wamp.close.normal":
            self.log.info("Shutting down ..")
            # user initiated leave => end the program
            self.config.runner.stop()
            self.disconnect()
        else:
            # continue running the program (let ApplicationRunner perform auto-reconnect attempts ..)
            self.log.info("Will continue to run (reconnect)!")

    def onDisconnect(self):
        self.log.info("{klass}.onDisconnect()", klass=self.__class__.__name__)

        try:
            reactor.stop()
        except ReactorNotRunning:
            pass


if __name__ == "__main__":
    parser = argparse.ArgumentParser()

    parser.add_argument("-d", "--debug", action="store_true", help="Enable debug output.")

    parser.add_argument(
        "--url",
        dest="url",
        type=str,
        default="ws://localhost:8080/ws",
        help='The router URL (default: "ws://localhost:8080/ws").',
    )

    parser.add_argument(
        "--realm", dest="realm", type=str, default="xbr", help='The realm to join (default: "realm1").'
    )

    parser.add_argument(
        "--ethkey", dest="ethkey", type=str, help="Private Ethereum key (32 bytes as HEX encoded string)"
    )

    parser.add_argument(
        "--cskey",
        dest="cskey",
        type=str,
        help="Private WAMP-cryptosign authentication key (32 bytes as HEX encoded string)",
    )

    parser.add_argument(
        "--email",
        dest="email",
        type=str,
        default="somebody@nodomain",
        help="Member email address (the one used to register the member in the first place).",
    )

    parser.add_argument(
        "--wallet",
        dest="wallet",
        type=str,
        default="E11BA2b4D45Eaed5996Cd0823791E0C93114882d",
        help="HEX encoded member wallet address (the one used to register the member in the first place).",
    )

    args = parser.parse_args()

    if args.debug:
        txaio.start_logging(level="debug")
    else:
        txaio.start_logging(level="info")

    extra = {
        "ethkey": binascii.a2b_hex(args.ethkey),
        "cskey": binascii.a2b_hex(args.cskey),
        "member_email": args.email,
        "wallet_adr": args.wallet,
    }

    runner = ApplicationRunner(url=args.url, realm=args.realm, extra=extra, serializers=[CBORSerializer()])

    try:
        runner.run(XbrDelegate, auto_reconnect=True)
    except Exception as e:
        print(e)
        sys.exit(1)
    else:
        sys.exit(0)
